#include "preHeader.h"
#include "Spell.h"
#include "Map/MapInstance.h"
#include "tile/InrangeIterator.h"

const Spell::SpellTargetTypeSelector Spell::m_spellTargetTypeSelectors[] = {
	nullptr,
	&Spell::SelectTargetSelf,
	&Spell::SelectTargetFriend,
	&Spell::SelectTargetEnemy,
	&Spell::SelectTargetFriends,
	&Spell::SelectTargetEnemies,
	&Spell::SelectTargetSelfOrFriend,
	&Spell::SelectTargetFocusFriends,
	&Spell::SelectTargetFocusEnemies,
	&Spell::SelectTargetTeamMember,
	&Spell::SelectTargetTeamMembers,
	&Spell::SelectTargetSelfOrTeamMember,
	&Spell::SelectTargetFocusTeamMembers,
	nullptr,
};

const Spell::SpellTargetModeSelector Spell::m_spellTargetModeSelectors[] = {
	&Spell::SelectTargetCircle,
	&Spell::SelectTargetSector,
	&Spell::SelectTargetRect,
};

void Spell::SelectTargetByMode(SPELL_SELECT_MODE_ARGS) const
{
	STATIC_ASSERT(ARRAY_SIZE(
		m_spellTargetTypeSelectors) == (int)SpellSelectType::Count);
	STATIC_ASSERT(ARRAY_SIZE(
		m_spellTargetModeSelectors) == (int)SpellSelectMode::Count);
	if (sleiInfo.spellSelectNumber != 0 &&
		targetList.size() >= sleiInfo.spellSelectNumber) {
		return;
	}
	if (sleiInfo.spellSelectMode < (u8)SpellSelectMode::Count) {
		if (m_spellTargetModeSelectors[sleiInfo.spellSelectMode] != NULL) {
			(this->*m_spellTargetModeSelectors[sleiInfo.spellSelectMode])
				(test, tgtEffPos, targetList, sleiInfo);
		}
	}
}

vector3f Spell::CalcSelectTarget_offsetDir(const vector3f& tgtEffPos) const
{
	auto tgtEffDir = tgtEffPos - m_pCaster->GetPosition();
	if (FLOAT_EQUALS_ZERO(tgtEffDir.Length2DSq())) {
		tgtEffDir = m_pCaster->GetDirection();
	}
	tgtEffDir.y = 0.f;
	tgtEffDir.normalize();
	return tgtEffDir;
}

const vector3f& Spell::CalcSelectTarget_tgtEffNewPos() const
{
	auto pLObj = m_targetGuid == ObjGUID_NULL ? NULL :
		m_pCaster->GetMapInstance()->GetLocatableObject(m_targetGuid);
	if (pLObj != NULL) {
		return m_tgtEffNewPos = pLObj->GetPosition();
	}
	if (m_tgtEffNewPos.y > INT_MAX) {
		return m_pCaster->GetPosition();
	}
	return m_tgtEffNewPos;
}

void Spell::SelectTargetSelf(SPELL_SELECT_TYPE_ARGS) const
{
	targetList.insert(m_pCaster->GetGuid());
}

void Spell::SelectTargetFriend(SPELL_SELECT_TYPE_ARGS) const
{
	auto pUnit = m_targetGuid == ObjGUID_NULL ? NULL :
		m_pCaster->GetMapInstance()->GetUnit(m_targetGuid);
	if (pUnit != NULL && m_pCaster->IsFriend(pUnit)) {
		targetList.insert(m_targetGuid);
	}
}

void Spell::SelectTargetEnemy(SPELL_SELECT_TYPE_ARGS) const
{
	auto pUnit = m_targetGuid == ObjGUID_NULL ? NULL :
		m_pCaster->GetMapInstance()->GetUnit(m_targetGuid);
	if (pUnit != NULL && m_pCaster->IsHostile(pUnit)) {
		targetList.insert(m_targetGuid);
	}
}

void Spell::SelectTargetFriends(SPELL_SELECT_TYPE_ARGS) const
{
	SelectTargetSelf(targetList, sleiInfo);
	SelectTargetByMode([=](Unit* pTarget) {
		return m_pCaster->IsFriend(pTarget);
	}, m_pCaster->GetPosition(), targetList, sleiInfo);
}

void Spell::SelectTargetEnemies(SPELL_SELECT_TYPE_ARGS) const
{
	SelectTargetByMode([=](Unit* pTarget) {
		return m_pCaster->IsHostile(pTarget);
	}, m_pCaster->GetPosition(), targetList, sleiInfo);
}

void Spell::SelectTargetSelfOrFriend(SPELL_SELECT_TYPE_ARGS) const
{
	if (m_targetGuid != ObjGUID_NULL) {
		SelectTargetFriend(targetList, sleiInfo);
	}
	if (targetList.empty()) {
		SelectTargetSelf(targetList, sleiInfo);
	}
}

void Spell::SelectTargetFocusFriends(SPELL_SELECT_TYPE_ARGS) const
{
	if (m_targetGuid != ObjGUID_NULL) {
		SelectTargetFriend(targetList, sleiInfo);
	}
	SelectTargetByMode([=](Unit* pTarget) {
		return m_pCaster == pTarget ||
			m_pCaster->IsFriend(pTarget);
	}, CalcSelectTarget_tgtEffNewPos(), targetList, sleiInfo);
}

void Spell::SelectTargetFocusEnemies(SPELL_SELECT_TYPE_ARGS) const
{
	if (m_targetGuid != ObjGUID_NULL) {
		SelectTargetEnemy(targetList, sleiInfo);
	}
	SelectTargetByMode([=](Unit* pTarget) {
		return m_pCaster->IsHostile(pTarget);
	}, CalcSelectTarget_tgtEffNewPos(), targetList, sleiInfo);
}

void Spell::SelectTargetTeamMember(SPELL_SELECT_TYPE_ARGS) const
{
	if (m_pCaster->IsType(TYPE_PLAYER)) {
		auto pPlayer = static_cast<Player*>(m_pCaster);
		if (pPlayer->IsPlayTeamMember(m_targetGuid)) {
			targetList.insert(m_targetGuid);
		}
	}
}

void Spell::SelectTargetTeamMembers(SPELL_SELECT_TYPE_ARGS) const
{
	if (m_pCaster->IsType(TYPE_PLAYER)) {
		auto pPlayer = static_cast<Player*>(m_pCaster);
		SelectTargetSelf(targetList, sleiInfo);
		SelectTargetByMode([=](Unit* pTarget) {
			return pPlayer->IsPlayTeamMember(pTarget->GetGuid());
		}, m_pCaster->GetPosition(), targetList, sleiInfo);
	}
}

void Spell::SelectTargetSelfOrTeamMember(SPELL_SELECT_TYPE_ARGS) const
{
	if (m_pCaster->IsType(TYPE_PLAYER)) {
		if (m_targetGuid != ObjGUID_NULL) {
			SelectTargetTeamMember(targetList, sleiInfo);
		}
		if (targetList.empty()) {
			SelectTargetSelf(targetList, sleiInfo);
		}
	}
}

void Spell::SelectTargetFocusTeamMembers(SPELL_SELECT_TYPE_ARGS) const
{
	if (m_pCaster->IsType(TYPE_PLAYER)) {
		auto pPlayer = static_cast<Player*>(m_pCaster);
		if (m_targetGuid != ObjGUID_NULL) {
			SelectTargetTeamMember(targetList, sleiInfo);
		}
		SelectTargetByMode([=](Unit* pTarget) {
			return pPlayer == pTarget ||
				pPlayer->IsPlayTeamMember(pTarget->GetGuid());
		}, CalcSelectTarget_tgtEffNewPos(), targetList, sleiInfo);
	}
}

void Spell::SelectTargetCircle(SPELL_SELECT_MODE_ARGS) const
{
	// args: radius(0)
	InrangeIterator itr(&m_pCaster->GetMapInstance()->sTileHandler,
		tgtEffPos.x, tgtEffPos.z, sleiInfo.spellSelectArgs[0]);
	itr.ForeachActor([&, this](TileActor* pActor) {
		auto pLObj = static_cast<LocatableObject*>(pActor);
		if (!pLObj->IsKindOf(TYPE_UNIT)) {
			return false;
		}
		auto pUnit = static_cast<Unit*>(pLObj);
		if (pUnit->IsDead() &&
			!sleiInfo.spellEffectFlags.isTargetDeadable) {
			return false;
		}
		if (!test(pUnit)) {
			return false;
		}
		if (targetList.insert(pUnit->GetGuid()).second) {
			if (targetList.size() >= sleiInfo.spellSelectNumber) {
				return true;
			}
		}
		return false;
	});
}

void Spell::SelectTargetSector(SPELL_SELECT_MODE_ARGS) const
{
	// args: radius(0),angle(1),offset(2),hole(3)
	auto dir = CalcSelectTarget_offsetDir(tgtEffPos);
	float o = dir.Calc2DAngle();
	vector3f tgtEffRePos =
		tgtEffPos + dir * sleiInfo.spellSelectArgs[2];
	InrangeIterator itr(&m_pCaster->GetMapInstance()->sTileHandler,
		tgtEffRePos.x, tgtEffRePos.z, sleiInfo.spellSelectArgs[0]);
	itr.ForeachActor([&, this, o](TileActor* pActor) {
		auto pLObj = static_cast<LocatableObject*>(pActor);
		if (!pLObj->IsKindOf(TYPE_UNIT)) {
			return false;
		}
		auto pUnit = static_cast<Unit*>(pLObj);
		if (pUnit->IsDead() &&
			!sleiInfo.spellEffectFlags.isTargetDeadable) {
			return false;
		}
		float holeDistSQ = SQ(sleiInfo.spellSelectArgs[3]);
		if (pUnit->GetDistance2DSq(tgtEffRePos) < holeDistSQ) {
			return false;
		}
		float includedAngle = Calc2DIncludedAngle(
			(pUnit->GetPosition() - tgtEffRePos).Calc2DAngle(), o);
		if (std::abs(includedAngle) > sleiInfo.spellSelectArgs[1] / 2) {
			return false;
		}
		if (!test(pUnit)) {
			return false;
		}
		if (targetList.insert(pUnit->GetGuid()).second) {
			if (targetList.size() >= sleiInfo.spellSelectNumber) {
				return true;
			}
		}
		return false;
	});
}

void Spell::SelectTargetRect(SPELL_SELECT_MODE_ARGS) const
{
	// args: length(0),width(1),offset(2)
	auto dir = CalcSelectTarget_offsetDir(tgtEffPos);
	float o = dir.Calc2DAngle();
	float x = sleiInfo.spellSelectArgs[1] / 2;
	float z = sleiInfo.spellSelectArgs[0] / 2;
	float diameter = std::sqrt(
		SQ(sleiInfo.spellSelectArgs[0]) + SQ(sleiInfo.spellSelectArgs[1]));
	vector3f tgtEffRePos = tgtEffPos + dir *
		(sleiInfo.spellSelectArgs[2] + sleiInfo.spellSelectArgs[0] / 2);
	InrangeIterator itr(&m_pCaster->GetMapInstance()->sTileHandler,
		tgtEffRePos.x, tgtEffRePos.z, diameter / 2);
	itr.ForeachActor([&, this, o, x, z](TileActor* pActor) {
		auto pLObj = static_cast<LocatableObject*>(pActor);
		if (!pLObj->IsKindOf(TYPE_UNIT)) {
			return false;
		}
		auto pUnit = static_cast<Unit*>(pLObj);
		if (pUnit->IsDead() &&
			!sleiInfo.spellEffectFlags.isTargetDeadable) {
			return false;
		}
		auto& rawPos = pUnit->GetPosition();
		auto varyPos = vector3f_rotateXZBy(rawPos - tgtEffRePos, -o);
		if (!IsInRange(varyPos.x, -x, x) || !IsInRange(varyPos.z, -z, z)) {
			return false;
		}
		if (!test(pUnit)) {
			return false;
		}
		if (targetList.insert(pUnit->GetGuid()).second) {
			if (targetList.size() >= sleiInfo.spellSelectNumber) {
				return true;
			}
		}
		return false;
	});
}
